package system.util;

import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.rmi.NotBoundException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ExportException;
import java.rmi.server.RemoteObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import sun.reflect.generics.tree.BottomSignature;
import system.DebugOptionInterface;
import system.agent.AgentInterface;
import system.agent.CommunicationAgentInterface;

/* The server which allows agents execution. For each arriving agent, a
   socket is created on which the agent state and code are read.
   A thread is also created for the execution of the agent.
   A new class loader is created and associated with the agent.
   The agent code is integrated in this class loader.

   main - the main entry point of the server. It creates a socket
   on port passed in parameter on which the incoming agents are received.
 */

public class Server extends RemoteObject implements DebugOptionInterface {	

	/**
	 * List of the agents currently on the server
	 */
	private final static Map<String, CommunicationAgentInterface> agents = new HashMap<String, CommunicationAgentInterface>(); 

	private final static int portOfLocalRegistry = 1499;

	/**
	 * port of the server
	 */
	private int port;

	/**
	 * hostName of the server
	 */
	private String hostName;

	/**
	 * Semaphore to synchronize the management of the list of agents
	 */
	private static Object synchro = new Object();

	/**
	 * Instance of the server (no more than one instance by JVM)
	 */
	private static Server serverInstance;

	private Server(int port) {
		this.port = port;
		try {
			hostName = InetAddress.getLocalHost().getHostName();
			if (OPTION) { 
				System.out.println("Server pret sur l'host " + hostName + ":" + port);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void startServer() {
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					ServerSocket s = new ServerSocket(port);

					//Accept incoming agent and create a thread which receives the agent state and code and deals with them.
					while (true) {
						Socket soc = s.accept();
						Thread t2 = new Thread(new serverThread(soc));
						t2.start();
					}
				}
				catch(BindException be){
					System.out.println("Le port " + port + " est deja occupé, pas de création de nouveau server");
					serverInstance = null;
				}
				catch (Exception ex) {
					System.out.println("Server(main) error: " + ex.getMessage());
					ex.printStackTrace();
					return;
				}
			}
		});
		t1.start();
	}

	public static void main(String args[]) {
		int port;

		try { // transformation d'une chaine de caracteres en entier
			Integer I = new Integer(args[0]);
			port = I.intValue();
		} catch (Exception ex) {
			System.out.println("Please enter: java server <port>");
			return;
		}
		synchronized (synchro) {
			if(serverInstance == null){
				serverInstance = new Server(port);
				serverInstance.startServer();
			}
		}
	}


	/** 
	 * Return the interface of the agent present of the serveur with the specified name
	 * of null if it does not exist
	 * @param agentName name of the agent to retrieve
	 * @return the agent present of the serveur with the specified name
	 */
	public static CommunicationAgentInterface getAgent(String agentName) {
		synchronized (synchro) {
			return Server.agents.get(agentName);
		}
	}

	private static void removeAgent(String agentName){
		synchronized (synchro) {
			Server.agents.remove(agentName);
		}
	}

	/**
	 * Return a collection of all the agents staying on the server.
	 * @return a collection of all the agents staying on the server.
	 */
	public static Collection<CommunicationAgentInterface> getAllAgents() {
		synchronized (synchro) {			
			return new ArrayList<CommunicationAgentInterface>(Server.agents.values());
		}
	}

	/**
	 * Register a new agent on the server, if no server is present on the JVM, create a new one.
	 * @param agentName name of the agent
	 * @param agent the agent
	 */
	public static void registerAgent(String agentName, CommunicationAgentInterface agent){
		synchronized (synchro) {
			if(serverInstance == null){
				serverInstance = new Server(agent.getCurrentPort());
				serverInstance.startServer();
			}
			Server.agents.put(agentName, agent);
		}
	}

	public static void unregister(String agentName){
		synchronized (synchro) {
			Server.removeAgent(agentName);
		}
	}

	/* The server thread of an agent */
	private class serverThread implements Runnable, DebugOptionInterface {

		Socket s;

		/* The server thread receives the agent state on socket s and initialize the agent with the reference to the name server (n).
		 */
		public serverThread (Socket s) {
			this.s = s;
		}

		@Override
		public void run () {
			try {
				/* Create a class loader for the agent */
				Loader l = new Loader();
				//Load the starter class which is used to deserialize the agent state. Create an instance of the starter and run it.
				l.loadStarter();
				Class c = l.loadClass(StarterImpl.class.getName(), true);
				StarterInterface starter = (StarterInterface)c.newInstance();
				starter.startAgent(this.s);

			} catch (Exception ex) {
				System.out.println("Server(run) error : " + ex.getMessage());
				ex.printStackTrace();
				return;
			}
		}
	}
}
